/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.remoting.jaxws;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.ProtocolException;
import javax.xml.ws.Service;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.soap.SOAPFaultException;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteConnectFailureException;
import org.springframework.remoting.RemoteLookupFailureException;
import org.springframework.remoting.RemoteProxyFailureException;
/**
* {@link org.aopalliance.intercept.MethodInterceptor} for accessing a
* specific port of a JAX-WS service.
*
* <p>Uses either {@link LocalJaxWsServiceFactory}'s facilities underneath,
* or takes an explicit reference to an existing JAX-WS Service instance
* (e.g. obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}).
*
* @author Juergen Hoeller
* @since 2.5
* @see #setPortName
* @see #setServiceInterface
* @see javax.xml.ws.Service#getPort
* @see org.springframework.remoting.RemoteAccessException
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public class JaxWsPortClientInterceptor extends LocalJaxWsServiceFactory
implements MethodInterceptor, InitializingBean {
private Service jaxWsService;
private String portName;
private String username;
private String password;
private String endpointAddress;
private boolean maintainSession;
private boolean useSoapAction;
private String soapActionUri;
private Map<String, Object> customProperties;
private Class<?> serviceInterface;
private boolean lookupServiceOnStartup = true;
private QName portQName;
private Object portStub;
private final Object preparationMonitor = new Object();
/**
* Set a reference to an existing JAX-WS Service instance,
* for example obtained via {@link org.springframework.jndi.JndiObjectFactoryBean}.
* If not set, {@link LocalJaxWsServiceFactory}'s properties have to be specified.
* @see #setWsdlDocumentUrl
* @see #setNamespaceUri
* @see #setServiceName
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
public void setJaxWsService(Service jaxWsService) {
this.jaxWsService = jaxWsService;
}
/**
* Return a reference to an existing JAX-WS Service instance, if any.
*/
public Service getJaxWsService() {
return this.jaxWsService;
}
/**
* Set the name of the port.
* Corresponds to the "wsdl:port" name.
*/
public void setPortName(String portName) {
this.portName = portName;
}
/**
* Return the name of the port.
*/
public String getPortName() {
return this.portName;
}
/**
* Set the username to specify on the stub.
* @see javax.xml.ws.BindingProvider#USERNAME_PROPERTY
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Return the username to specify on the stub.
*/
public String getUsername() {
return this.username;
}
/**
* Set the password to specify on the stub.
* @see javax.xml.ws.BindingProvider#PASSWORD_PROPERTY
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Return the password to specify on the stub.
*/
public String getPassword() {
return this.password;
}
/**
* Set the endpoint address to specify on the stub.
* @see javax.xml.ws.BindingProvider#ENDPOINT_ADDRESS_PROPERTY
*/
public void setEndpointAddress(String endpointAddress) {
this.endpointAddress = endpointAddress;
}
/**
* Return the endpoint address to specify on the stub.
*/
public String getEndpointAddress() {
return this.endpointAddress;
}
/**
* Set the "session.maintain" flag to specify on the stub.
* @see javax.xml.ws.BindingProvider#SESSION_MAINTAIN_PROPERTY
*/
public void setMaintainSession(boolean maintainSession) {
this.maintainSession = maintainSession;
}
/**
* Return the "session.maintain" flag to specify on the stub.
*/
public boolean isMaintainSession() {
return this.maintainSession;
}
/**
* Set the "soapaction.use" flag to specify on the stub.
* @see javax.xml.ws.BindingProvider#SOAPACTION_USE_PROPERTY
*/
public void setUseSoapAction(boolean useSoapAction) {
this.useSoapAction = useSoapAction;
}
/**
* Return the "soapaction.use" flag to specify on the stub.
*/
public boolean isUseSoapAction() {
return this.useSoapAction;
}
/**
* Set the SOAP action URI to specify on the stub.
* @see javax.xml.ws.BindingProvider#SOAPACTION_URI_PROPERTY
*/
public void setSoapActionUri(String soapActionUri) {
this.soapActionUri = soapActionUri;
}
/**
* Return the SOAP action URI to specify on the stub.
*/
public String getSoapActionUri() {
return this.soapActionUri;
}
/**
* Set custom properties to be set on the stub.
* <p>Can be populated with a String "value" (parsed via PropertiesEditor)
* or a "props" element in XML bean definitions.
* @see javax.xml.ws.BindingProvider#getRequestContext()
*/
public void setCustomProperties(Map<String, Object> customProperties) {
this.customProperties = customProperties;
}
/**
* Allow Map access to the custom properties to be set on the stub,
* with the option to add or override specific entries.
* <p>Useful for specifying entries directly, for example via
* "customProperties[myKey]". This is particularly useful for
* adding or overriding entries in child bean definitions.
*/
public Map<String, Object> getCustomProperties() {
if (this.customProperties == null) {
this.customProperties = new HashMap<String, Object>();
}
return this.customProperties;
}
/**
* Add a custom property to this JAX-WS BindingProvider.
* @param name the name of the attribute to expose
* @param value the attribute value to expose
* @see javax.xml.ws.BindingProvider#getRequestContext()
*/
public void addCustomProperty(String name, Object value) {
getCustomProperties().put(name, value);
}
/**
* Set the interface of the service that this factory should create a proxy for.
*/
public void setServiceInterface(Class serviceInterface) {
if (serviceInterface != null && !serviceInterface.isInterface()) {
throw new IllegalArgumentException("'serviceInterface' must be an interface");
}
this.serviceInterface = serviceInterface;
}
/**
* Return the interface of the service that this factory should create a proxy for.
*/
public Class getServiceInterface() {
return this.serviceInterface;
}
/**
* Set whether to look up the JAX-WS service on startup.
* <p>Default is "true". Turn this flag off to allow for late start
* of the target server. In this case, the JAX-WS service will be
* lazily fetched on first access.
*/
public void setLookupServiceOnStartup(boolean lookupServiceOnStartup) {
this.lookupServiceOnStartup = lookupServiceOnStartup;
}
public void afterPropertiesSet() {
if (this.lookupServiceOnStartup) {
prepare();
}
}
public void prepare() {
if (getServiceInterface() == null) {
throw new IllegalArgumentException("Property 'serviceInterface' is required");
}
Service serviceToUse = getJaxWsService();
if (serviceToUse == null) {
serviceToUse = createJaxWsService();
}
this.portQName = getQName(getPortName() != null ? getPortName() : getServiceInterface().getName());
Object stub = (getPortName() != null ?
serviceToUse.getPort(this.portQName, getServiceInterface()) : serviceToUse.getPort(getServiceInterface()));
preparePortStub(stub);
this.portStub = stub;
}
/**
* Return whether this client interceptor has already been prepared,
* i.e. has already looked up the JAX-WS service and port.
*/
protected boolean isPrepared() {
synchronized (this.preparationMonitor) {
return (this.portStub != null);
}
}
/**
* Return the prepared QName for the port.
* @see #setPortName
* @see #getQName
*/
protected final QName getPortQName() {
return this.portQName;
}
/**
* Prepare the given JAX-WS port stub, applying properties to it.
* Called by {@link #prepare}.
* @param stub the current JAX-WS port stub
* @see #setUsername
* @see #setPassword
* @see #setEndpointAddress
* @see #setMaintainSession
* @see #setCustomProperties
*/
protected void preparePortStub(Object stub) {
Map<String, Object> stubProperties = new HashMap<String, Object>();
String username = getUsername();
if (username != null) {
stubProperties.put(BindingProvider.USERNAME_PROPERTY, username);
}
String password = getPassword();
if (password != null) {
stubProperties.put(BindingProvider.PASSWORD_PROPERTY, password);
}
String endpointAddress = getEndpointAddress();
if (endpointAddress != null) {
stubProperties.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpointAddress);
}
if (isMaintainSession()) {
stubProperties.put(BindingProvider.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE);
}
if (isUseSoapAction()) {
stubProperties.put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE);
}
String soapActionUri = getSoapActionUri();
if (soapActionUri != null) {
stubProperties.put(BindingProvider.SOAPACTION_URI_PROPERTY, soapActionUri);
}
stubProperties.putAll(getCustomProperties());
if (!stubProperties.isEmpty()) {
if (!(stub instanceof BindingProvider)) {
throw new RemoteLookupFailureException("Port stub of class [" + stub.getClass().getName() +
"] is not a customizable JAX-WS stub: it does not implement interface [javax.xml.ws.BindingProvider]");
}
((BindingProvider) stub).getRequestContext().putAll(stubProperties);
}
}
/**
* Return the underlying JAX-WS port stub that this interceptor delegates to
* for each method invocation on the proxy.
*/
protected Object getPortStub() {
return this.portStub;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
if (AopUtils.isToStringMethod(invocation.getMethod())) {
return "JAX-WS proxy for port [" + getPortName() + "] of service [" + getServiceName() + "]";
}
// Lazily prepare service and stub if necessary.
synchronized (this.preparationMonitor) {
if (!isPrepared()) {
prepare();
}
}
return doInvoke(invocation);
}
/**
* Perform a JAX-WS service invocation based on the given method invocation.
* @param invocation the AOP method invocation
* @return the invocation result, if any
* @throws Throwable in case of invocation failure
* @see #getPortStub()
* @see #doInvoke(org.aopalliance.intercept.MethodInvocation, Object)
*/
protected Object doInvoke(MethodInvocation invocation) throws Throwable {
try {
return doInvoke(invocation, getPortStub());
}
catch (SOAPFaultException ex) {
throw new JaxWsSoapFaultException(ex);
}
catch (ProtocolException ex) {
throw new RemoteConnectFailureException("Could not connect to remote service [" + this.portQName + "]", ex);
}
catch (WebServiceException ex) {
throw new RemoteAccessException("Could not access remote service at [" + this.portQName + "]", ex);
}
}
/**
* Perform a JAX-WS service invocation on the given port stub.
* @param invocation the AOP method invocation
* @param portStub the RMI port stub to invoke
* @return the invocation result, if any
* @throws Throwable in case of invocation failure
* @see #getPortStub()
*/
protected Object doInvoke(MethodInvocation invocation, Object portStub) throws Throwable {
Method method = invocation.getMethod();
try {
return method.invoke(portStub, invocation.getArguments());
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
catch (Throwable ex) {
throw new RemoteProxyFailureException("Invocation of stub method failed: " + method, ex);
}
}
}